package com.blockchain.securewallet.model.db;

import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;

import com.blockchain.securewallet.WalletApp;
import com.blockchain.securewallet.btc.Address;
import com.blockchain.securewallet.btc.BTCUtils;
import com.blockchain.securewallet.btc.BitcoinException;
import com.blockchain.securewallet.btc.KeyPair;
import com.blockchain.securewallet.btc.Transaction;
import com.blockchain.securewallet.btc.UnspentOutputInfo;
import com.blockchain.securewallet.ltc.BCCParams;
import com.blockchain.securewallet.ltc.LTCParams;
import com.blockchain.securewallet.model.SendRecord;
import com.blockchain.securewallet.utils.ByteUtils;
import com.blockchain.securewallet.utils.EException;
import com.blockchain.securewallet.utils.Md5;
import com.blockchain.securewallet.utils.Point;
import com.blockchain.securewallet.utils.Ripemd160;
import com.blockchain.securewallet.utils.Secp256k;
import com.blockchain.securewallet.utils.ToastUtils;

import org.bitcoinj.core.Base58;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import org.bitcoinj.crypto.HDUtils;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.wallet.DeterministicKeyChain;
import org.bitcoinj.wallet.DeterministicSeed;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Unique;
import org.spongycastle.util.encoders.Hex;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.Wallet;
import org.web3j.crypto.WalletFile;
import org.web3j.utils.Numeric;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Entity
public class CoinWallet {

    private static final String TAG = "Wallet.Wallet";

    public static final String PATH_BITCOIN = "M/44H/0H/0H/0/0";
    public static final String PATH_ETHER = "m/44'/60'/0'/0/0";
    public static final String PATH_LTC = "M/44H/2H/0H/0/0";
    public static final String PATH_BCC = "M/44H/145H/0H/0/0";
    public static final String PATH_EOS = "M/44H/194H/0H/0/0";

    private static NetworkParameters params;
    //private static NetworkParameters params = TestNet3Params.get();

    public final static int TYPE_BIT_COIN = 0;
    public final static int TYPE_ETH_COIN = 1;
    public final static int TYPE_LTC_COIN = 3;
    public final static int TYPE_BCH_COIN = 4;
    public final static int TYPE_EOS_COIN = 5;

    public static final int BIT_COIN_WALLET_TYPE = Address.PUBLIC_KEY_TO_ADDRESS_LEGACY;

    //币种的id
    private int id;
    private String seed;
    @Unique
    private String address;
    private String privateKey;
    private String publicKey;
    private String password;
    private String words;
    private long value = 0L;
    private int type;


    @Generated(hash = 250088140)
    public CoinWallet(int id, String seed, String address, String privateKey, String publicKey, String password, String words, long value, int type) {
        this.id = id;
        this.seed = seed;
        this.address = address;
        this.privateKey = privateKey;
        this.publicKey = publicKey;
        this.password = password;
        this.words = words;
        this.value = value;
        this.type = type;
    }

    @Generated(hash = 787922076)
    public CoinWallet() {
    }


    public static List<CoinWallet> createAllWallet(String words, String password){
        long creationTimeSeconds = System.currentTimeMillis() / 1000;
//        words="beach collect nest purse stadium timber riot noodle lunch cricket drill web";
//        password="22222";
        List<String> listWords = Arrays.asList(words.split("\\s+"));
        DeterministicSeed seed = new DeterministicSeed(listWords, null, password, creationTimeSeconds);

        List<CoinWallet> coinWalletList=new ArrayList<>();
        coinWalletList.add(createBitCoinWallet(seed,words,password));
        coinWalletList.add(createEtherWallet(seed,words,password));
        coinWalletList.add(createLtcWallet(seed,words,password));
        coinWalletList.add(createBccWallet(seed,words,password));
//        coinWalletList.add(createEosWallet(seed,words,password));

        return coinWalletList;
    }

    private static CoinWallet createEosWallet(DeterministicSeed seed, String words, String password) {
        DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
        byte[] seeds = keyChain.getKeyByPath(HDUtils.parsePath(PATH_EOS), true).getPrivKeyBytes();
//        if (seeds == null || seeds.length() == 0) {
//            throw new EException("args_empty", "args is empty");
//        }
        byte[] a = { (byte) 0x80 };
        byte[] b = new BigInteger(Sha256Hash.hash(seeds)).toByteArray();
        byte[] private_key = ByteUtils.concat(a, b);
        byte[] checksum = Sha256Hash.hash(private_key);
        checksum = Sha256Hash.hash(checksum);
        byte[] check = ByteUtils.copy(checksum, 0, 4);
        byte[] pk = ByteUtils.concat(private_key, check);
        String privateKey = Base58.encode(pk);
//        privateKey="5Jd6Zhcv1DWCZkRHb6pGNjpRAYZ5m2HBDC3u555jCuCD3nmnUgZ";


        // private key
        BigInteger d = privateKey(privateKey);
        // publick key
        Secp256k secp = new Secp256k();
        Point ep = secp.G().multiply(d);
        byte[] pub_buf = ep.getEncoded();
        byte[] csum = Ripemd160.from(pub_buf).bytes();
        csum = ByteUtils.copy(csum, 0, 4);
        byte[] addy = ByteUtils.concat(pub_buf, csum);
        String address_prefix = "EOS";
        StringBuffer bf = new StringBuffer(address_prefix);
        bf.append(Base58.encode(addy));
        String publicKey = bf.toString();
        Log.i("eos",privateKey+"              "+publicKey);

        CoinWallet wallet = new CoinWallet();
        wallet.setId(4);
        wallet.setAddress(publicKey);
        wallet.setPassword(password);
        wallet.setPrivateKey(privateKey);
        wallet.setPublicKey(publicKey);
        wallet.setWords(words);
        wallet.setType(TYPE_EOS_COIN);
        wallet.setSeed(Hex.toHexString(seeds));
        return wallet;


    }

    private static BigInteger privateKey(String pk) {
        byte[] private_wif = Base58.decode(pk);
        byte version = (byte) 0x80;
        if (private_wif[0] != version) {
            throw new EException("version_error", "Expected version " + 0x80 + ", instead got " + version);
        }
        byte[] private_key = ByteUtils.copy(private_wif, 0, private_wif.length - 4);
        byte[] new_checksum = Sha256Hash.hash(private_key);
        new_checksum = Sha256Hash.hash(new_checksum);
        new_checksum = ByteUtils.copy(new_checksum, 0, 4);
        byte[] last_private_key = ByteUtils.copy(private_key, 1, private_key.length - 1);
        BigInteger d = new BigInteger(com.blockchain.securewallet.utils.Hex.bytesToHexString(last_private_key), 16);
        return d;
    }




    public static CoinWallet createLtcWallet(DeterministicSeed seed,String words, String password) {
        params = LTCParams.get();
        DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
//        keyChain.getKeyByPath(HDUtils.parsePath(PATH_BITCOIN),true);
        byte[] seeds = keyChain.getKeyByPath(HDUtils.parsePath(PATH_LTC), true).getPrivKeyBytes();
        ECKey ecKey = ECKey.fromPrivate(seeds);
        String publicKey = Numeric.toHexStringNoPrefixZeroPadded(new BigInteger(ecKey.getPubKey()),66);
        String privateKey = ecKey.getPrivateKeyEncoded(params).toString();
        String address = ecKey.toAddress(params).toString();
        Log.i(TAG,"pubbb:"+publicKey+"\npriv:"+privateKey+" \naddress:"+address);
        CoinWallet wallet = new CoinWallet();

        wallet.setId(2);
        wallet.setAddress(address);
        wallet.setPassword(password);
        wallet.setPrivateKey(privateKey);
        wallet.setPublicKey(publicKey);
        wallet.setWords(words);
        wallet.setType(TYPE_LTC_COIN);
        wallet.setSeed(Hex.toHexString(seeds));
        return wallet;
    }

    public static CoinWallet createBccWallet(DeterministicSeed seed,String words, String password) {
        params = BCCParams.get();
        DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
//        keyChain.getKeyByPath(HDUtils.parsePath(PATH_BITCOIN),true);
        byte[] seeds = keyChain.getKeyByPath(HDUtils.parsePath(PATH_BCC), true).getPrivKeyBytes();
        ECKey ecKey = ECKey.fromPrivate(seeds);
        String publicKey = Numeric.toHexStringNoPrefixZeroPadded(new BigInteger(ecKey.getPubKey()),66);
        String privateKey = ecKey.getPrivateKeyEncoded(params).toString();
        String address = ecKey.toAddress(params).toString();
        Log.i("createBccWallet","pubbb:"+publicKey+"\npriv:"+privateKey+" \naddress:"+address);
        CoinWallet wallet = new CoinWallet();

        wallet.setId(3);
        wallet.setAddress(address);
        wallet.setPassword(password);
        wallet.setPrivateKey(privateKey);
        wallet.setPublicKey(publicKey);
        wallet.setWords(words);
        wallet.setType(TYPE_BCH_COIN);
        wallet.setSeed(Hex.toHexString(seeds));
        return wallet;
    }





    public static CoinWallet createBitCoinWallet(DeterministicSeed seed,String words, String password) {
        params = MainNetParams.get();


        initWallet(seed);

        DeterministicKeyChain deterministicKeyChain = DeterministicKeyChain.builder().seed(seed).build();
        byte[] seeds = deterministicKeyChain.getKeyByPath(HDUtils.parsePath(PATH_BITCOIN), true).getPrivKeyBytes();
        //byte[] seeds= Hex.decode("4c1f97e603e7e1ea0c93886cb7299adb02789246f9aeb1f195c3136ea0e7291b");
        KeyPair keyPair = BTCUtils.generateWifKey(false, CoinWallet.BIT_COIN_WALLET_TYPE, seeds);
        CoinWallet wallet = new CoinWallet();
        wallet.setId(0);
        wallet.setPassword(password);
        wallet.setAddress(keyPair.address.addressString);
        wallet.setPrivateKey(keyPair.privateKey.privateKeyEncoded);
        wallet.setPublicKey(Hex.toHexString(keyPair.publicKey));
        wallet.setWords(words);
        wallet.setSeed(Hex.toHexString(seeds));
        wallet.setType(TYPE_BIT_COIN);
        return wallet;
    }

    private static void initWallet(DeterministicSeed ds) {
        String hexSeed = ds.toHexString();

        String md5Random = Md5.MD5(hexSeed);
        int postion = Integer.parseInt(String.valueOf(md5Random.charAt(7)).toLowerCase(), 16);

        String addRandomSeed = hexSeed.substring(0, postion) + md5Random + hexSeed.substring(postion, hexSeed.length());

        String md5Seed = Md5.MD5(Md5.MD5(addRandomSeed));

        SharedPreferences sf = WalletApp.getInstance().getSharedPreferences(WalletApp.getInstance().getPackageName(), Context.MODE_PRIVATE);
        WalletApp.uuid = sf.getString(WalletApp.KEY_ID, "");


        if (TextUtils.isEmpty(WalletApp.uuid) || !WalletApp.uuid.equals(md5Seed)) {
            SharedPreferences.Editor editor = sf.edit();
            editor.putString(WalletApp.KEY_ID, md5Seed);
            WalletApp.uuid = md5Seed;
            editor.apply();
        }

        //判断是否存在重复id
        WalletApp.queryIdAndDelete(WalletApp.uuid);


        //删除手势密码
        WalletApp.getInstance().saveLockPasswordSet(false);
    }

    public static CoinWallet createEtherWallet(DeterministicSeed seed,String words, String password) {

        String[] pathArray = PATH_ETHER.split("/");
        byte[] seeds = seed.getSeedBytes();
        if (seeds == null)
            return null;
        DeterministicKey dkKey = HDKeyDerivation.createMasterPrivateKey(seeds);
        for (int i = 1; i < pathArray.length; i++) {
            ChildNumber childNumber;
            if (pathArray[i].endsWith("'")) {
                int number = Integer.parseInt(pathArray[i].substring(0,
                        pathArray[i].length() - 1));
                childNumber = new ChildNumber(number, true);
            } else {
                int number = Integer.parseInt(pathArray[i]);
                childNumber = new ChildNumber(number, false);
            }
            dkKey = HDKeyDerivation.deriveChildKey(dkKey, childNumber);
        }
        ECKeyPair keyPair = ECKeyPair.create(dkKey.getPrivKeyBytes());
        String privateKey = keyPair.getPrivateKey().toString(16);
        String publicKey = keyPair.getPublicKey().toString(16);
        String address = null;
        try {
            WalletFile walletFile = Wallet.createLight(password, keyPair);
            address = "0x" + walletFile.getAddress();
        } catch (CipherException e) {
            return null;
        }
        CoinWallet wallet = new CoinWallet();
        wallet.setId(1);
        wallet.setAddress(address);
        wallet.setPassword(password);
        wallet.setPrivateKey(privateKey);
        wallet.setPublicKey(publicKey);
        wallet.setWords(words);
        wallet.setType(TYPE_ETH_COIN);
        wallet.setSeed(Hex.toHexString(seeds));
        return wallet;
    }

    public String signedEthTransactionData(String to, BigInteger nonce, BigInteger gasPrice, BigInteger gasLimit, BigInteger value, String data) {
        RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, data);
        String privateKey = getPrivateKey();
        if (privateKey.startsWith("0x")) {
            privateKey = privateKey.substring(2);
        }
        ECKeyPair keyPair = ECKeyPair.create(new BigInteger(privateKey, 16));
        Credentials credentials = Credentials.create(keyPair);
        byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
        return Numeric.toHexString(signedMessage);
    }

    public String signedBitCoinTransactionData(List<UnspentOutputInfo> unspentOutputs,
                                               String outputAddress,
                                               String changeAddress,
                                               final long amount,
                                               final float satoshisPerVirtualByte,
                                               @BTCUtils.TransactionType int transactionType) {
        Transaction btcSpendTx = null;
        try {
            btcSpendTx = BTCUtils.createTransaction(unspentOutputs, outputAddress, address, amount, satoshisPerVirtualByte, transactionType);
            //6. double check that generated transaction is valid
            Transaction.Script[] relatedScripts = new Transaction.Script[btcSpendTx.inputs.length];
            long[] amounts = new long[btcSpendTx.inputs.length];
            for (int i = 0; i < btcSpendTx.inputs.length; i++) {
                Transaction.Input input = btcSpendTx.inputs[i];
                for (UnspentOutputInfo unspentOutput : unspentOutputs) {
                    if (Arrays.equals(unspentOutput.txHash, input.outPoint.hash) && unspentOutput.outputIndex == input.outPoint.index) {
                        relatedScripts[i] = unspentOutput.scriptPubKey;
                        amounts[i] = unspentOutput.value;
                        break;
                    }
                }
            }
            BTCUtils.verify(relatedScripts, amounts, btcSpendTx, false);
        } catch (BitcoinException e) {
            return "";
        } catch (Exception e) {
            return "";
        }
        long inValue = 0;
        for (Transaction.Input input : btcSpendTx.inputs) {
            for (UnspentOutputInfo unspentOutput : unspentOutputs) {
                if (Arrays.equals(unspentOutput.txHash, input.outPoint.hash) && unspentOutput.outputIndex == input.outPoint.index) {
                    inValue += unspentOutput.value;
                }
            }
        }
        long outValue = 0;
        for (Transaction.Output output : btcSpendTx.outputs) {
            outValue += output.value;
        }
        long fee = inValue - outValue;
        GenerateTransactionResult result = new GenerateTransactionResult(btcSpendTx, fee);
        if (result.btcTx != null) {
            return BTCUtils.toHex(result.btcTx.getBytes());
        }
        return "";
    }

    public String signedBitCoinMultiTransactionData(List<UnspentOutputInfo> unspentOutputs,
                                                    List<SendRecord> records,
                                                    final float satoshisPerVirtualByte,
                                                    @BTCUtils.TransactionType int transactionType) {
        Transaction btcSpendTx = null;
        try {
            btcSpendTx = BTCUtils.createMultiTransaction(unspentOutputs, records, address, satoshisPerVirtualByte, transactionType);
            //6. double check that generated transaction is valid
            Transaction.Script[] relatedScripts = new Transaction.Script[btcSpendTx.inputs.length];
            long[] amounts = new long[btcSpendTx.inputs.length];
            for (int i = 0; i < btcSpendTx.inputs.length; i++) {
                Transaction.Input input = btcSpendTx.inputs[i];
                for (UnspentOutputInfo unspentOutput : unspentOutputs) {
                    if (Arrays.equals(unspentOutput.txHash, input.outPoint.hash) && unspentOutput.outputIndex == input.outPoint.index) {
                        relatedScripts[i] = unspentOutput.scriptPubKey;
                        amounts[i] = unspentOutput.value;
                        break;
                    }
                }
            }
            BTCUtils.verify(relatedScripts, amounts, btcSpendTx, false);
        } catch (BitcoinException e) {
            ToastUtils.showToast(e.getMessage());
            return "";
        } catch (Exception e) {
            return "";
        }
        long inValue = 0;
        for (Transaction.Input input : btcSpendTx.inputs) {
            for (UnspentOutputInfo unspentOutput : unspentOutputs) {
                if (Arrays.equals(unspentOutput.txHash, input.outPoint.hash) && unspentOutput.outputIndex == input.outPoint.index) {
                    inValue += unspentOutput.value;
                }
            }
        }
        long outValue = 0;
        for (Transaction.Output output : btcSpendTx.outputs) {
            outValue += output.value;
        }
        long fee = inValue - outValue;
        GenerateTransactionResult result = new GenerateTransactionResult(btcSpendTx, fee);
        if (result.btcTx != null) {
            return BTCUtils.toHex(result.btcTx.getBytes());
        }
        return "";
    }

    public String signedLtcCoinTransactionData(List<UnspentOutputInfo> unspentOutputs,
                                               List<SendRecord> records,
                                               final float satoshisPerVirtualByte,
                                               @BTCUtils.TransactionType int transactionType){

        Transaction btcSpendTx = null;
        try {
            btcSpendTx = BTCUtils.createMultiTransaction(unspentOutputs, records, address, satoshisPerVirtualByte, transactionType);
            //6. double check that generated transaction is valid
            Transaction.Script[] relatedScripts = new Transaction.Script[btcSpendTx.inputs.length];
            long[] amounts = new long[btcSpendTx.inputs.length];
            for (int i = 0; i < btcSpendTx.inputs.length; i++) {
                Transaction.Input input = btcSpendTx.inputs[i];
                for (UnspentOutputInfo unspentOutput : unspentOutputs) {
                    if (Arrays.equals(unspentOutput.txHash, input.outPoint.hash) && unspentOutput.outputIndex == input.outPoint.index) {
                        relatedScripts[i] = unspentOutput.scriptPubKey;
                        amounts[i] = unspentOutput.value;
                        break;
                    }
                }
            }
            BTCUtils.verify(relatedScripts, amounts, btcSpendTx, false);
        } catch (BitcoinException e) {
            ToastUtils.showToast(e.getMessage());
            return "";
        } catch (Exception e) {
            return "";
        }
        long inValue = 0;
        for (Transaction.Input input : btcSpendTx.inputs) {
            for (UnspentOutputInfo unspentOutput : unspentOutputs) {
                if (Arrays.equals(unspentOutput.txHash, input.outPoint.hash) && unspentOutput.outputIndex == input.outPoint.index) {
                    inValue += unspentOutput.value;
                }
            }
        }
        long outValue = 0;
        for (Transaction.Output output : btcSpendTx.outputs) {
            outValue += output.value;
        }
        long fee = inValue - outValue;
        GenerateTransactionResult result = new GenerateTransactionResult(btcSpendTx, fee);
        if (result.btcTx != null) {
            return BTCUtils.toHex(result.btcTx.getBytes());
        }
        return "";


    }

    @Override
    public String toString() {
        return "CoinWallet{" +
                "id=" + id +
                ", seed='" + seed + '\'' +
                ", address='" + address + '\'' +
                ", privateKey='" + privateKey + '\'' +
                ", publicKey='" + publicKey + '\'' +
                ", password='" + password + '\'' +
                ", words='" + words + '\'' +
                ", value=" + value +
                ", type=" + type +
                '}';
    }

    public int getId() {
        return this.id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getSeed() {
        return this.seed;
    }

    public void setSeed(String seed) {
        this.seed = seed;
    }

    public String getAddress() {
        return this.address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPrivateKey() {
        return this.privateKey;
    }

    public void setPrivateKey(String privateKey) {
        this.privateKey = privateKey;
    }

    public String getPublicKey() {
        return this.publicKey;
    }

    public void setPublicKey(String publicKey) {
        this.publicKey = publicKey;
    }

    public String getPassword() {
        return this.password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getWords() {
        return this.words;
    }

    public void setWords(String words) {
        this.words = words;
    }

    public long getValue() {
        return this.value;
    }

    public void setValue(long value) {
        this.value = value;
    }

    public int getType() {
        return this.type;
    }

    public void setType(int type) {
        this.type = type;
    }



    static class GenerateTransactionResult {
        static final int ERROR_SOURCE_UNKNOWN = 0;
        static final int ERROR_SOURCE_INPUT_TX_FIELD = 1;
        static final int ERROR_SOURCE_ADDRESS_FIELD = 2;
        static final int HINT_FOR_ADDRESS_FIELD = 3;
        static final int ERROR_SOURCE_AMOUNT_FIELD = 4;

        final Transaction btcTx;
        final String errorMessage;
        final int errorSource;
        final long fee;

        GenerateTransactionResult(String errorMessage, int errorSource) {
            btcTx = null;
            this.errorMessage = errorMessage;
            this.errorSource = errorSource;
            fee = -1;
        }

        GenerateTransactionResult(Transaction btcTx, long fee) {
            this.btcTx = btcTx;
            errorMessage = null;
            errorSource = ERROR_SOURCE_UNKNOWN;
            this.fee = fee;
        }
    }


}
